developmentor - a developer services company				  
				      Essential .NET Security
   Modules

Conventional Crypto

Estimated time for completion: 60 minutes


Goals of this exercise:

  • Get some hands on experience using System.Security.Cryptography
  • Create symmetrical keys from passwords
  • Measure entropy of passwords
  • Encrypt and decrypt files

Overview

In this activity you will work with files and passwords, deriving a conventional key from a password, and using that key to encrypt the file. Many helper functions have been provided so you can focus on the cryptography, but you'll get to pick the algorithm and key length. You also have the option of requiring strong passwords.


part 1- Encrypting and Decrypting Files

In this activity, you'll collect a filename and password from the user and encrypt or decrypt the file using a symmetric key you derive from the password.

steps:

  1. Open the shell project by clicking here.

  2. Open the source file FileEncryptor.cs and familiarize yourself with the code that's already been provided for you. This includes some simple command line parsing, and a function you might find handy that requests a password (of the strength you specify) from the user without echoing it to the console as the user types it. There are also a couple other helper functions that will be described a bit later to help relieve your fingers of the tedium of typing.

  3. Find the _encrypt function. Note that it already opens the source file and creates a temporary file to which we'll dump the ciphertext. Your job in the next few steps is to implement this function.

  4. Pick a symmetric encryption algorithm you'd like to use. The slides from the lecture list the available algorithms. For our purposes, the one you pick doesn't matter, as long as you use the same algorithm to encrypt and decrypt. Once you've picked the one you want to use, create an instance of the algorithm object, set the Mode property to CipherMode.CBC, the Padding property to PaddingMode.PKCS7, and call GenerateIV() to create a random initialization vector (IV). An IV is a block of data that will be used to seed the feedback loop of the cipher - see Bruce Schneier's book Applied Cryptography if you are curious how this works.

  5. You'll need the initialization vector when it comes time to decrypt, so go ahead and write that out to the output stream by calling _writeIV() function. The IV is not anything that needs to be kept secret, so just writing this out with the encrypted file is a safe technique to use.

  6. Now it's time to get a password from the user so we can come up with some key material to use for the encryption. Call the _getPassword() function to prompt the user for a password. If you pass a number greater than 0 for the requiredEntropy parameter, this function will do its best to ensure the user provides a password with at least that strength. You might start with a low number during implementation, then raise it after you've gotten the code debugged.

    Pass the string you get back (the password) to the _keyFromPassword() function, and store the result in a byte array (don't fret, you'll fill in the implementation for the _keyFromPassword function in a moment).

    This byte array is the key you'll use for the encryption, so assign it to your algorithm's Key property.

  7. You're now ready to encrypt. You'll need to create a CryptoStream object to do this. The CryptoStream will need to have a reference to the underlying raw output stream where the ciphertext will be sent, so pass that to the constructor. You'll also need to pass an "encryptor" object provided by your symmetric algorithm. You can call CreateEncryptor() to get this. Finally, since you are pushing data rather than pulling it through the CryptoStream, the third constructor argument should be CryptoStreamMode.Write.

  8. Now all you need to do is pump data from the input stream (plaintext) through the cryptostream and into the tempfile as ciphertext. This is such a common operation that I've provided a helper function called _pump() to do this for you. Call it to empty the input stream into the output stream. Don't forget that the output stream in this case is the CryptoStream, not the raw output stream.

  9. Close your CryptoStream object, and note that the raw streams are closed for you at the end of the function, and the temporary file with ciphertext is used to replace the old file which contained the plaintext. You're done!

  10. Well, almost. You now need to finish implementing the _keyFromPassword function. Let's do that now.

    You're going to hash the string passed to you by the user, then take the some bytes from that hash to make up your key. The key length will depend on the algorithm. For instance, for Rijndael, the default key size is 16 bytes long (128 bits). If you used a different algorithm, check the algorithm's KeyLength property to see how many bytes you'll need for the key. Some algorithms allow you to set the KeyLength - if you're interested, you can look at the LegalKeySizes property of the algorithm you've chosen.

    The first thing you'll need to do is convert the password string into a binary stream of data that you can hash. I've provided a helper function that does this for you called _stringToMemoryStream. Call it, passing in the password string to get a MemoryStream containing the password data. Now create an instance of SHA1CryptoServiceProvider, and pass the MemoryStream you just got to ComputeHash, and grab the resulting byte array. This is an array of 20 bytes, but you will likely only need 16 (once again, check the KeyLength property of your algorithm to be sure), so copy the first N bytes of the hash into a new array. Here's some code you can use to do this (this code assumes a 16 byte KeyLength):

    byte[] key = new byte[16];
    Array.Copy(shaHash, 0, key, 0, key.Length);

    Finally, return the 16 byte array as output.

  11. Build and test your encryption code. You should be able to run your code from the command line like this:

    FileEncryptor /encrypt somefile.txt
  12. Now implement the _decrypt function. To do this, create an instance of the algorithm, set its Mode and Padding just like you did in _encrypt, and call _readIV to read the initialization vector from the input stream.

    Set the key by asking the user for the password using the same functions you did earlier, but this time pass 0 for the required entropy, since we aren't creating a new password, rather asking the user to recall an existing password.

    Create a CryptoStream based on this algorithm, with the raw output stream as its output. Pump the raw input stream into the CryptoStream, then close the CryptoStream.

  13. Build and test your decryption code. You should be able to run your code from the command line like this:

    FileEncryptor /decrypt somefile.txt

part 2- Challenge: Using a session key (optional)

In the first part of this lab, you encrypted the entire file with a password-based key. Passwords and phrases are usually long term keys, so it's not a good idea to encrypt boatloads of data using them. In this optional challenge, you must generate a random session key, use that to encrypt the contents of the file, then encrypt the session key with the password. Because this is a challenge, only basic instructions will be provided.

steps:

  1. Generate a random session key (hint: RNGCryptoServiceProvider is your friend).

  2. Generate a CryptoStream based on the password. You'll use this to encrypt the session key (which is 16 bytes long if you choose a 128 bit session key).

  3. Set the encryption mode for this stream to ECB, since we don't require feedback when encrypting the session key. This just simplifies things so you don't need to record an initialization vector (IV) for your encrypted session key. Also, you can simplify things a bit by turning off padding for this CryptoStream, as long as the session key you're encrypting is an exact multiple of the block size you're using to encrypt it. For instance, a 128 bit key (16 bytes) doesn't need any padding if your algorithm is using an 8 or 16 byte block size.

  4. Pump the session key through the CryptoStream, thus encrypting it with the password and streaming it out to disk. Don't Close() this CryptoStream, just Flush() it.

  5. Create a second CryptoStream based on the session key, and use it to encrypt the file contents to disk. You'll want to use PKCS7 padding and the CBC cipher mode in this case, so you'll need to generate and record an IV. (HINT: you've already got most of this code written from the previous lab).

  6. Write the corresponding decryption code.

  7. Build and test!

Solutions: